﻿// Autore: Alessio Parma

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;

namespace RecipesManager.Data.Operations
{
    sealed class GuestClassifier
    {
        readonly IDictionary<IGuest, IDictionary<IGuest, Issue>> _guestIssues;
        readonly ObservableCollection<RatedGuest> _ratedColl = new ObservableCollection<RatedGuest>();
        readonly IDictionary<IGuest, RatedGuest> _ratedDict = new Dictionary<IGuest, RatedGuest>();
        readonly IDictionary<IGuest, int> _relCount = new Dictionary<IGuest, int>();
        readonly IDictionary<IGuest, int> _scores = new Dictionary<IGuest, int>();
        readonly Classifier _classifier;

        public GuestClassifier(IList<IGuest> guests, Classifier classifier)
        {
            _classifier = classifier;
            _guestIssues = new Dictionary<IGuest, IDictionary<IGuest, Issue>>();

            var d = guests.ToDictionary<IGuest, IGuest, Issue>(og => og, og => null);

            foreach (var g in guests)
            {
                _scores.Add(g, 0);
                _relCount.Add(g, 0);

                _guestIssues.Add(g, new Dictionary<IGuest, Issue>(d));

                var rg = new RatedGuest(g);
                _ratedDict.Add(g, rg);
                _ratedColl.Add(rg);
            }
        }

        public ObservableCollection<RatedGuest> RatedGuests
        {
            get { return _ratedColl; }
        }

        public void OnGuestAdd(IGuest guest)
        {
            // Non lo rimuoviamo da _scores perché
            // potrebbe tornare utile per degli warning
            _ratedColl.Remove(_ratedDict[guest]);

            foreach (var r in guest.GuestRelationships)
            {
                CheckRelationshipOnAdd(guest, r);
                var count = _relCount[r] + 1;
                _relCount[r] = count;
                if (count > 1) continue;
                _ratedDict[r].Type = GuestType.Necessary;
            }

            foreach (var r in guest.GuestRatings)
            {
                var other = r.Entity;
                CheckRatingOnAdd(guest, other, r.Rating);
                var score = _scores[other] + Utils.GuestRatingToInt(r.Rating);
                _scores[other] = score;
                if (_relCount[other] > 0) continue;
                _ratedDict[other].Type = Utils.GuestTypeForScore(score);
            }

            foreach (var r in guest.OtherGuestRatings)
                CheckRatingOnAdd(r.Entity, guest, r.Rating);         
        }

        public void OnGuestRemove(IGuest guest)
        {
            foreach (var r in guest.OtherGuestRatings)
                CheckRatingOnRemove(r.Entity, guest, r.Rating);

            foreach (var r in guest.GuestRatings)
            {
                CheckRatingOnRemove(guest, r.Entity, r.Rating);
                var score = _scores[r.Entity] - Utils.GuestRatingToInt(r.Rating);
                _scores[r.Entity] = score;
                if (_relCount[r.Entity] > 0) continue;
                _ratedDict[r.Entity].Type = Utils.GuestTypeForScore(score);
            }

            foreach (var r in guest.GuestRelationships)
            {
                CheckRelationshipOnRemove(guest, r);
                var count = _relCount[r] - 1;
                _relCount[r] = count;
                if (count > 0) continue;
                _ratedDict[r].Type = Utils.GuestTypeForScore(_scores[r]);
            }
           
            _ratedColl.Add(_ratedDict[guest]);
        }

        void CheckRatingOnAdd(IGuest whoRates, IGuest whoIsRated, GuestRating rating)
        {
            if (rating != GuestRating.Bad && rating != GuestRating.VeryBad) return;
            if (!_classifier.Contains(whoIsRated) || !_classifier.Contains(whoRates)) return;

            var issue = Issue.ForGuestRating(whoRates, whoIsRated, rating);

            var whoRatesIssues = _guestIssues[whoRates];
            if (whoRatesIssues[whoIsRated] != null) return;
            whoRatesIssues[whoIsRated] = issue;
            _classifier.Issues.Add(issue);
        }

        void CheckRatingOnRemove(IGuest whoRates, IGuest whoIsRated, GuestRating rating)
        {
            if (rating != GuestRating.Bad && rating != GuestRating.VeryBad) return;
            if (!_classifier.Contains(whoIsRated) || !_classifier.Contains(whoRates)) return;

            var whoRatesIssues = _guestIssues[whoRates];
            var issue = whoRatesIssues[whoIsRated];
            if (issue == null) return;
            _classifier.Issues.Remove(issue);
            whoRatesIssues[whoIsRated] = null;
        }

        void CheckRelationshipOnAdd(IGuest toAdd, IGuest rel)
        {
            Issue issue;
            if (_classifier.Contains(rel))
            {
                var relIssues = _guestIssues[rel];
                issue = relIssues[toAdd];
                if (issue != null)
                {
                    _classifier.Issues.Remove(issue);
                    relIssues.Remove(toAdd);
                }
                return;
            }
            issue = Issue.ForRelationship(toAdd, rel);
            _guestIssues[toAdd][rel] = issue;
            _classifier.Issues.Add(issue);
        }

        void CheckRelationshipOnRemove(IGuest toRemove, IGuest rel)
        {
            Issue issue;
            if (!_classifier.Contains(rel))
            {
                var toRemoveIssues = _guestIssues[toRemove];
                issue = toRemoveIssues[rel];
                if (issue != null)
                {
                    _classifier.Issues.Remove(issue);
                    toRemoveIssues.Remove(rel);
                }
                return;
            }
            issue = Issue.ForRelationship(rel, toRemove);
            _guestIssues[rel][toRemove] = issue;
            _classifier.Issues.Add(issue);
        }

        [ContractInvariantMethod]
        void Invariant()
        {
            Contract.Invariant(RatedGuests != null);
        }
    }
}